package dac.zip;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author yangdc
 * @date 2025/7/18
 * 文件压缩工具类，用于将多个文件打包成ZIP文件。
 */
public class ZipUtils {

    private static final int BUFFER_SIZE = 1024 * 8; // 8KB 缓冲区

    /**
     * 将指定的文件列表打包成一个ZIP文件。
     *
     * @param files       要压缩的文件列表，每个File对象代表一个文件。
     * @param zipFilePath 生成的ZIP文件的完整路径和文件名（例如："./output/myarchive.zip"）。
     * @param parentPath  （可选）在ZIP条目名称中作为共同父目录的前缀。
     *                    如果传入 "path/to/source/"，则文件 "path/to/source/a.txt" 在zip中会显示为 "a.txt"。
     *                    如果传入 null 或空字符串，则显示为完整路径。
     *                    注意：此参数应以操作系统的文件分隔符结尾（例如 "/" 或 "\ "）。
     * @throws IOException 如果在文件读写过程中发生I/O错误。
     */
    public static void zipFiles(List<File> files, String zipFilePath, String parentPath) throws IOException {
        if (files == null || files.isEmpty()) {
            System.out.println("No files to zip. Exiting.");
            return;
        }

        File zipFile = new File(zipFilePath);
        // 确保输出目录存在
        File outputDir = zipFile.getParentFile();
        if (outputDir != null && !outputDir.exists()) {
            outputDir.mkdirs();
        }

        try (FileOutputStream fos = new FileOutputStream(zipFile);
             ZipOutputStream zos = new ZipOutputStream(fos)) {

            for (File file : files) {
                if (!file.exists() || !file.isFile()) {
                    System.err.println("Warning: Skipping non-existent or non-file entry: " + file.getAbsolutePath());
                    continue;
                }

                // 2. 创建ZipEntry，设置条目名称
                // 条目名称是在ZIP包中文件显示的路径。
                String entryName;
                if (parentPath != null && !parentPath.isEmpty() && file.getAbsolutePath().startsWith(parentPath)) {
                    // 如果文件路径以父路径开头，则去除父路径部分
                    entryName = file.getAbsolutePath().substring(parentPath.length());
                } else {
                    entryName = file.getName(); // 默认只取文件名
                }
                // 在Windows系统上，需要将路径分隔符统一，通常ZIP内部使用 '/'
                entryName = entryName.replace('\\', '/');

                ZipEntry zipEntry = new ZipEntry(entryName);
                zos.putNextEntry(zipEntry); // 放入下一个条目

                // 3. 读取文件内容并写入ZIP流
                try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file), BUFFER_SIZE)) {
                    byte[] buffer = new byte[BUFFER_SIZE];
                    int bytesRead;
                    while ((bytesRead = bis.read(buffer)) != -1) {
                        zos.write(buffer, 0, bytesRead);
                    }
                }
                zos.closeEntry(); // 关闭当前条目
                System.out.println("Added to zip: " + file.getAbsolutePath() + " as " + entryName);
            }
            System.out.println("Successfully created zip file: " + zipFilePath);

        } catch (IOException e) {
            System.err.println("Error creating zip file: " + e.getMessage());
            throw e; // 重新抛出异常，让调用者处理
        }
    }

//    /**
//     * 辅助方法：生成一个要压缩的文件列表。
//     * 实际应用中这些文件列表可能来自数据库查询、目录扫描等。
//     */
//    public static List<File> createDummyFilesForTest(String baseDir) throws IOException {
//        // 创建一个用于测试的目录和文件
//        File dir = new File(baseDir);
//        if (!dir.exists()) {
//            dir.mkdirs();
//        }
//
//        File file1 = new File(dir, "document1.txt");
//        File file2 = new File(dir, "image.jpg");
//        File file3 = new File(dir, "subdir" + File.separator + "report.pdf");
//        File file4 = new File(dir, "subdir" + File.separator + "another.txt");
//
//        // 确保文件存在且有内容
//        createFileWithContent(file1, "This is the content of document 1.");
//        createFileWithContent(file2, "Dummy image content (not actual image data)."); // 实际中应为图片二进制数据
//        file3.getParentFile().mkdirs(); // 创建子目录
//        createFileWithContent(file3, "This is a dummy PDF report.");
//        createFileWithContent(file4, "Another text file in a sub-directory.");
//
//        return List.of(file1, file2, file3, file4);
//    }

//    private static void createFileWithContent(File file, String content) throws IOException {
//        try (FileOutputStream fos = new FileOutputStream(file)) {
//            fos.write(content.getBytes());
//        }
//    }

//    public static void main(String[] args) {
//        // 1. 定义待压缩文件的父目录和输出ZIP文件路径
//        String sourceBaseDir = "./temp_files_to_zip";
//        String outputZipPath = "./output/my_archive.zip";
//
//        try {
//            // 2. 生成待压缩文件列表
//            // 在实际应用中，这里会是你程序中获取文件列表的逻辑，
//            // 可能是从一个数据库查询结果，或者扫描某个目录获取
//            List<File> filesToZip = createDummyFilesForTest(sourceBaseDir);
//
//            // 3. 执行压缩操作
//            // 场景1: 默认将文件名作为zip内的条目名
//            System.out.println("\n--- Scenario 1: Zipping files with default names ---");
//            // 注意：这里没有指定parentPath，因此在zip中将是文件本身的名称
//            // 例如：document1.txt, image.jpg, report.pdf, another.txt
//            // 因为createDummyFilesForTest创建的文件都在temp_files_to_zip下，所以它们在zip里将直接是文件名，不会包含subdirs
//            // 如果希望包含子目录，则需要调整 createDummyFilesForTest 返回的file路径或者 parentPath逻辑
//            zipFiles(filesToZip, "./output/archive_simple.zip", null);
//
//
//            // 场景2: 以某个共同的父目录为基准，保留相对路径结构。
//            System.out.println("\n--- Scenario 2: Zipping files with preserved directory structure ---");
//            // 如果希望在zip中保留 subdir/report.pdf 这样的结构，
//            // 那么 parentPath 应该设置为所有待压缩文件共同的逻辑根目录。
//            // 例如：如果 sourceBaseDir 是 "temp_files_to_zip"，
//            // 那么文件 "temp_files_to_zip/subdir/report.pdf" 移除 "temp_files_to_zip/" 后，
//            // 在zip中就只剩下 "subdir/report.pdf"。
//            // 确保 parentPath 以文件分隔符结尾
//            String commonParentPath = new File(sourceBaseDir).getCanonicalPath() + File.separator;
//            zipFiles(filesToZip, outputZipPath, commonParentPath);
//
//            System.out.println("\nAll zipping tasks completed successfully.");
//
//        } catch (IOException e) {
//            System.err.println("An error occurred during zipping: " + e.getMessage());
//            e.printStackTrace();
//        } finally {
//            // 清理生成的测试文件和目录 (可选)
//            System.out.println("\nCleaning up test files...");
//            deleteDirectory(new File(sourceBaseDir));
//            // deleteDirectory(new File("./output")); // 如果需要清理输出目录
//            System.out.println("Cleanup complete.");
//        }
//    }

    // 辅助方法：递归删除目录及其内容
    private static void deleteDirectory(File directory) {
        if (directory.isDirectory()) {
            File[] files = directory.listFiles();
            if (files != null) {
                for (File file : files) {
                    deleteDirectory(file);
                }
            }
        }
        if (directory.delete()) {
            System.out.println("Deleted: " + directory.getAbsolutePath());
        } else {
            System.err.println("Failed to delete: " + directory.getAbsolutePath());
        }
    }

    /**
     * 递归删除指定目录及其所有内容。
     *
     * @param directory 要删除的目录File对象。
     * @return 如果目录及其内容成功删除则返回true，否则返回false。
     */
    public static boolean deleteDirectoryRecursively(File directory) {
        if (!directory.exists()) {
            System.out.println("Directory does not exist: " + directory.getAbsolutePath());
            return true; // 目录不存在，视为删除成功
        }
        if (!directory.isDirectory()) {
            System.out.println("It's not a directory, trying to delete as file: " + directory.getAbsolutePath());
            return directory.delete(); // 如果是文件，直接删除
        }
        // 获取目录下的所有文件和子目录
        File[] files = directory.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    // 如果是子目录，递归删除
                    if (!deleteDirectoryRecursively(file)) {
                        System.err.println("Failed to delete sub-directory: " + file.getAbsolutePath());
                        return false; // 如果子目录删除失败，则整个操作失败
                    }
                } else {
                    // 如果是文件，直接删除
                    if (!file.delete()) {
                        System.err.println("Failed to delete file: " + file.getAbsolutePath());
                        return false; // 如果文件删除失败，则整个操作失败
                    }
                }
            }
        }
        // 所有内容都删除了，现在删除空目录本身
        return directory.delete();
    }
    /**
     * Java 7+ Nio.2 方法：使用 Files.walk() (推荐，更简洁高效)
     *
     * @param directoryPath 要删除的目录路径字符串。
     * @return 如果目录及其内容成功删除则返回true，否则返回false。
     */
    public static boolean deleteDirectoryUsingNio2(String directoryPath) {
        Path path = Paths.get(directoryPath);
        if (!Files.exists(path)) {
            System.out.println("Directory does not exist: " + path.toAbsolutePath());
            return true;
        }
        try {
            // Files.walk 从指定路径开始遍历文件树，返回一个 Stream<Path>
            // sorted(Comparator.reverseOrder()) 确保先处理子文件/目录，再处理父目录，
            // 这样在Files.delete时，父目录已经是空的
            Files.walk(path)
                    .sorted(Comparator.reverseOrder()) // 确保子目录和文件先被删除
                    .forEach(p -> {
                        try {
                            Files.delete(p);
                            System.out.println("Deleted: " + p);
                        } catch (IOException e) {
                            System.err.println("Failed to delete " + p + ": " + e.getMessage());
                            throw new RuntimeException("Deletion failed for " + p, e); // 转换为RuntimeException中断流
                        }
                    });
            return true;
        } catch (IOException | RuntimeException e) {
            System.err.println("Error deleting directory: " + e.getMessage());
            // 如果是Files.walk的IOException，或者forEach中抛出的RuntimeException
            return false;
        }
    }
}

